package com.ftl.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ftl.search.service.EsSearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class EsSearchServiceImpl implements EsSearchService {

    @Resource
    private ElasticsearchRestTemplate restTemplate;

    @Resource
    private RestHighLevelClient client;

    @Override
    public List search(String keyword, String value, Class<?> clazz) {
        NativeSearchQuery query = buildQuery(keyword, value);
        SearchHits<?> searchHits = restTemplate.search(query, clazz);
        return searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }

    @Override
    public Map<String, List> multiSearch(List<NativeSearchQuery> builders, List<Class<?>> classes, List<String> keys) {
        List searchHitsList = restTemplate.multiSearch(builders, classes);
        Map<String, List> result = new HashMap<>();
        for (int i = 0; i < searchHitsList.size(); i++) {
            result.put(keys.get(i), ((SearchHits<?>) (searchHitsList.get(i))).getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()));
        }
        return result;
    }

    @Override
    public NativeSearchQuery buildQuery(String keyword, String value) {
        BoolQueryBuilder boolQuery = new BoolQueryBuilder().must(QueryBuilders.termQuery(keyword + ".keyword", value));
        return new NativeSearchQueryBuilder()
                .withQuery(boolQuery)
                .withPageable(PageRequest.of(0, 3))
                .withHighlightFields(
                        new HighlightBuilder.Field(keyword).preTags("<em>").postTags("</em>")
                )
                .build();
    }

    @Override
    public boolean checkIndexExists(String indexName) {
        IndexOperations indexOperations = restTemplate.indexOps(IndexCoordinates.of(indexName));
        return indexOperations.exists();
    }

    @Override
    public void createIndex(String indexName) {
        IndexOperations indexOperations = restTemplate.indexOps(IndexCoordinates.of(indexName));
        if (!indexOperations.exists()) {
            indexOperations.create();
            indexOperations.refresh();
            log.info("索引 {} 创建成功", indexName);
        }
    }

    @Override
    public void deleteIndex(String indexName) {
        IndexOperations indexOperations = restTemplate.indexOps(IndexCoordinates.of(indexName));
        if (indexOperations.exists()) {
            indexOperations.delete();
            log.info("索引 {} 删除成功", indexName);
        }
    }

    @Override
    public void save(String indexName, Object obj) {
        restTemplate.save(obj, IndexCoordinates.of(indexName));
    }

    @Override
    public void updateByDocumentId(String indexName, String documentId, Object obj) {
        UpdateQuery updateQuery = UpdateQuery.builder(documentId).withDocument(Document.parse(JSON.toJSONString(obj))).build();
        restTemplate.update(updateQuery, IndexCoordinates.of(indexName));
    }

    @Override
    public void updatePartialFields(String indexName, String documentId, Object obj) {
        JSONObject target = (JSONObject) JSON.toJSON(obj);
        if (!StringUtils.hasText(documentId)) {
            documentId = target.getString("id");
        }
        if (!StringUtils.hasText(documentId)) {
            return;
        }
        Object source = selectByDocumentId(indexName, documentId);
        JSONObject merge = new JSONObject();
        if (!ObjectUtils.isEmpty(source)) {
            merge.putAll((JSONObject) JSON.toJSON(source));
        }
        merge.putAll(target);
        log.info(JSON.toJSONString(merge));
        save(indexName, JSONObject.toJSON(merge));
    }

    @Override
    public void deleteByDocumentId(String indexName, String documentId) {
        restTemplate.delete(documentId, IndexCoordinates.of(indexName));
    }

    @Override
    public void deleteByObject(String indexName, Object obj) {
        restTemplate.delete(obj, IndexCoordinates.of(indexName));
    }

    @Override
    public Object selectByDocumentId(String indexName, String documentId) {
        SearchRequest request = new SearchRequest(indexName);
        SearchSourceBuilder builder = new SearchSourceBuilder();
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("_id", documentId);
        builder.query(termQueryBuilder);
        builder.from(0);
        builder.size(1);
        log.info(builder.toString());
        request.source(builder);
        SearchResponse response;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
            List<Map<String, Object>> result = new ArrayList<>();
            org.elasticsearch.search.SearchHit[] hits = response.getHits().getHits();
            for (org.elasticsearch.search.SearchHit hit : hits) {
                result.add(hit.getSourceAsMap());
            }
            return result.size() > 0 ? result.get(0) : null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
